package ca.csf.dfc.poo.environnement;

import java.util.Random;

import ca.csf.dfc.poo.exceptions.IllegalPositionXException;
import ca.csf.dfc.poo.exceptions.IllegalPositionYException;
import ca.csf.dfc.poo.exceptions.IllegalRecompenseException;
import ca.csf.dfc.poo.exceptions.IllegalTableauDecisionException;
import ca.csf.dfc.poo.exceptions.InvalidDirectionException;

/**
 * Robot qui se d�place de fa�on intelligente selon les valeurs des cases qui l'entourent.
 * Il ne se d�place pas comme le RobotVorace, mais il cherche le plus de r�compense � long terme.
 * @author Équipe 1.
 *
 */
public class RobotIntelligent extends Robot {
    /**
     * Variable static contenant le facteur d'éloignement.
     */
    public static final double GAMMA = 0.9;
    /**
     * Constante qui contient la valeur en pourcentage qu'on va décrémenter à chaque fois que le robot prend une décision
     * au hasard, pour réduire sa chance de prendre une décision au hasard. Plus la valeur est élevée, plus le robot
     * va prendre des décisions intelligentes rapidement.
     */
    public static final double DECREMENTER_HASARD = 0.01;
    
	/**
	 * tableau � deux dimension qui � la largeur et la hauteur de l'environnemente qui
	 * a une troisi�me dimension qui stock les r�compenses pour chaques direction dans laquelle il peut aller.
	 */
	protected double[][][] m_TabRecompenses;
	
	/**
	 * Chance en % que le robot � de prendre un chemin au hasard.
	 */
	protected double m_ProbabiliteHasard;
	
	/**
	 * Constructeur d'initialisation du RobotIntelligent qui construit un tableau pour permettre au robot
	 * de se d�placer dans une fa�on intelligente.
	 * @param p_X : position sur l'axe des abscisses du robot.
	 * @param p_Y : Position sur l'axe des ordonn�es du robot.
	 * @param p_LargeurEnvironnement : Largeur de l'environnement dans lequel le robot va se d�plac�.
	 * @param p_HauteurEnvironnement : Hauteur de l'environnement dans lequel le robot va se d�plac�.
	 * @throws IllegalRecompenseException 
	 * @throws IllegalPositionYException 
	 * @throws IllegalPositionXException 
	 */
	public RobotIntelligent(int p_X, int p_Y, int p_LargeurEnvironnement, int p_HauteurEnvironnement) throws IllegalPositionXException, IllegalPositionYException, IllegalRecompenseException
	{
		super(p_X,p_Y);
		m_TabRecompenses = new double[p_LargeurEnvironnement][p_HauteurEnvironnement][4];
		this.setHasard(1);
	}
	
	/**
	 * Permet de changer la probabilité que le robot prenne un chemin au hasard.
	 * @param p_Hasard : Nouvelle chance en % que le robot � de prendre un chemin au hasard
	 */
	protected void setHasard(double p_Hasard)
	{
	    if ((p_Hasard < 0) || (p_Hasard > 100))
	    {
	        throw new IllegalArgumentException("Le hasard doit être entre 0 et 100");
	    }
	    
		this.m_ProbabiliteHasard = p_Hasard;
	}
	
	/**
	 * Sert à obtenir la probabilité que le robot se déplace au hasard.
	 * @return La probalibité en %.
	 */
	public double getHasard()
	{
		return this.m_ProbabiliteHasard;
	}
	
	/**
	 * Permet de mettre une valeur dans le tableau selon les positions envoyé en paramètre.
	 * @param p_X : Position sur l'axe des X.
	 * @param p_Y : Position sur l'axe des Y.
	 * @param p_Z : Position sur l'axe des Z. (Représente la direction : Haut,Droite,Bas,Gauche)
	 * @param p_Valeur : Valeur à affectée.
	 * @throws IllegalPositionYException 
	 * @throws IllegalPositionXException 
	 */
	protected void setCaseTab(int p_X, int p_Y, int p_Z, double p_Valeur) throws IllegalPositionYException, IllegalPositionXException
	{
	    //On vérifie que les cases sont valides.
	    if ((p_X < 0) || (p_X >= m_TabRecompenses.length))
	        throw new IllegalPositionXException("La valeur ne doit pas dépasser "+m_TabRecompenses.length);
	    
        if ((p_Y < 0) || (p_Y >= m_TabRecompenses[0].length))
            throw new IllegalPositionYException("La valeur ne doit pas dépasser "+m_TabRecompenses[0].length);
        
	    if ((p_Z < 0) || (p_Z > 3))
	        throw new IllegalArgumentException("La valeur doit être comprise entre 0 et 3");
	    
	    this.m_TabRecompenses[p_X][p_Y][p_Z] = p_Valeur;
	}
	
	/**
	 * Permet d'obtenir une valeur désirée dans le tableau des récompenses accumulées du tableau.
	 * @param p_X : Position sur l'axe des X.
	 * @param p_Y : Position sur l'axe des Y.
	 * @param p_Z  : Position sur l'axe des Z. (Représente la direction : Haut,Droite,Bas,Gauche)
	 * @return La valeur contenu dans le tableau du robot à la position désirée.
	 * @throws IllegalPositionXException 
	 * @throws IllegalPositionYException 
	 */
	protected double getCaseTab(int p_X,int p_Y,int p_Z) throws IllegalPositionXException, IllegalPositionYException
	{
	  //On vérifie que les cases sont valides.
	    if ((p_X < 0) || (p_X >= m_TabRecompenses.length))
            throw new IllegalPositionXException("La valeur ne doit pas dépasser "+m_TabRecompenses.length);
        
        if ((p_Y < 0) || (p_Y >= m_TabRecompenses[0].length))
            throw new IllegalPositionYException("La valeur ne doit pas dépasser "+m_TabRecompenses[0].length);
        
        if ((p_Z < 0) || (p_Z > 3))
            throw new IllegalArgumentException("La valeur doit être comprise entre 0 et 3");
        
	    double valeur = this.m_TabRecompenses[p_X][p_Y][p_Z];
	    
	    return valeur;
	}
	
	/**
	 * Permet au robot de se déplacer. Le robot va soit se déplacer de façon aléatoire ou de façon
	 * intelligente selon ce qu'il connait de son environnement.
	 * Redéfinition.
	 * @return La décision choisie.
	 * @throws IllegalTableauDecisionException 
	 * @throws IllegalPositionYException 
	 * @throws IllegalPositionXException 
	 * @throws InvalidDirectionException 
	 * @see ca.csf.dfc.poo.environnement.Robot#prendreDecision(ca.csf.dfc.poo.environnement.Case[])
	 */
	@Override
	public int prendreDecision(Case[] p_TabCases) throws IllegalTableauDecisionException, IllegalPositionXException, IllegalPositionYException, InvalidDirectionException {
	    
	    //On regarde si le tableau ne contient pas 4 cases.
        if (p_TabCases.length != 4)
            throw new IllegalTableauDecisionException();
        
		//On décide si le robot va prendre une décision au hasard ou non.
	    Random r = new Random();
	    double hasard = r.nextDouble();
	    
	    //Si notre nombre est plus petit ou égale à m_ProbabiliteHasard, on prend un chemin au hasard.
	    int direction = AUCUNE;
	    
	    if (hasard <= this.getHasard())
	    {
	        direction = prendreDecisionHasard(p_TabCases);
	    }
	    else
	    {
	        //On vérifie que si il n'y a aucune valeur dans les cases entourant le robot, on va prendre
	        // une décision aléatoire.
	        boolean decisionAleatoire = true;
	        
	        //On vérifie pour les 4 cases entourant le robot dans les 4 directions pour vérifier si tous les cases sont accessibles
	        //Si elles le sont, on vérifie si leur récompense à long terme est > que 0 on prend une décision intelligente, sinon
	        // on garde notre décision au hasard.
	        for(int dir = 0;dir<p_TabCases.length;dir++)
	        {
	            if (p_TabCases[dir] != null)
	            { 
	                switch (dir)
	                {
    	                case HAUT:
    	                    if (this.getCaseTab(this.getX(), this.getY()-1, dir) > 0)
    	                           decisionAleatoire = false;
    	                    break;
    	                case DROITE:
    	                    if (this.getCaseTab(this.getX()+1, this.getY(), dir) > 0)
    	                        decisionAleatoire = false;
    	                    break;
    	                case BAS:
    	                    if (this.getCaseTab(this.getX(), this.getY()+1, dir) > 0)
    	                        decisionAleatoire = false;
    	                    break;
    	                case GAUCHE:
    	                    if (this.getCaseTab(this.getX()-1, this.getY(), dir) > 0)
    	                        decisionAleatoire = false;
    	                    break;
	                }
	            } 
	        }
	        
	        if (decisionAleatoire)
	            direction = prendreDecisionHasard(p_TabCases);
	        else
	            direction = prendreDecisionIntelligente(p_TabCases);
	    }
	    
	    //On déplace le robot et on enregitre la valeur de la case dans laquelle on va aller pour
	    // connaître notre environnement.
	    if (direction != RobotIntelligent.AUCUNE)
	    {
	        //On affecte une valeur à la case précédente, pour que le robot mémorise l'environnement.
	        memoriserValeurs(p_TabCases,direction);
	    }
	    
	    //On diminue les chance de prendre une décision au hasard de x%.
	    //On se garde toujours un 5% de hasard.
	    if (this.getHasard()*(1.0- RobotIntelligent.DECREMENTER_HASARD) >= 0.05)
	        this.setHasard(this.getHasard()*(1.0- RobotIntelligent.DECREMENTER_HASARD));
	    return direction;
	}
	
	/**
	 * Fonction qui permet au robot de se déplacer dans
	 * une direction au hasard.
	 * @param p_TabCases Contient les 4 cases entourant le robot.
	 * @return Retourne la dirction dans laquelle le robot va se déplacer.
	 * @throws IllegalTableauDecisionException 
	 */
	protected int prendreDecisionHasard(Case[] p_TabCases) throws IllegalTableauDecisionException
	{
	    //On regarde si le tableau ne contient pas 4 cases.
        if (p_TabCases.length != 4)
            throw new IllegalTableauDecisionException();
        
	    int direction = AUCUNE;
	    
	    //On vérifie qu'on a au moins un chemin de sortie.
	    if ((p_TabCases[HAUT] != null) || (p_TabCases[DROITE] != null) || 
	       (p_TabCases[BAS] != null) || (p_TabCases[GAUCHE] != null))
	    {
	        Random r = new Random();
            boolean directionValide = false;
            
            //On choisit un chiffre entre 0 et 3
            do
            {
                direction = r.nextInt(4);
                
                
                if(p_TabCases[direction]!= null)
                {
                    directionValide =true;
                }
                
            }
            while(!directionValide);
	    }
	    
        return direction;
	}
	
	/**
	 * Permet au robot de prendre la meilleure décision selon ce qu'il connait de l'environnement
	 * pour avoir la meilleure récompense possible à long terme.
	 * @param p_TabCases Contient les 4 cases entourant le robot.
	 * @return La direction la plus rentable que le robot peut prendre dans ce cas.
	 * @throws IllegalPositionYException 
	 * @throws IllegalPositionXException 
	 * @throws IllegalTableauDecisionException 
	 */
	protected int prendreDecisionIntelligente(Case[] p_TabCases) throws IllegalPositionXException, IllegalPositionYException, IllegalTableauDecisionException
    {
	    //On regarde si le tableau ne contient pas 4 cases.
        if (p_TabCases.length != 4)
            throw new IllegalTableauDecisionException();
        
	    int direction = AUCUNE;
        
        //On vérifie qu'on a au moins un chemin de sortie.
	    if ((p_TabCases[HAUT] != null) || (p_TabCases[DROITE] != null) || 
	            (p_TabCases[BAS] != null) || (p_TabCases[GAUCHE] != null))
        {
            //Si on a un chemin de sortie, on choisi la direction qui nous rapportera 
            // le plus de récompenses à long terme.
	        
            if (p_TabCases[HAUT] != null)
                direction = HAUT;
            
            
            if ((p_TabCases[DROITE] != null) && ((direction == AUCUNE) || (this.getCaseTab(this.getX(), this.getY(), DROITE) > this.getCaseTab(this.getX(), this.getY(), direction))))
            {
                direction = DROITE;
            }
            
            if ((p_TabCases[BAS] != null) && ((direction == AUCUNE) || (this.getCaseTab(this.getX(), this.getY(), BAS) > this.getCaseTab(this.getX(), this.getY(), direction))))
            {
                direction = BAS;
            }
            
            if ((p_TabCases[GAUCHE] != null) && ((direction == AUCUNE) || (this.getCaseTab(this.getX(), this.getY(), GAUCHE) > this.getCaseTab(this.getX(), this.getY(), direction))))
            {
                direction = GAUCHE;
            }
        }

        return direction;
    }
	
	/**
	 * Méthode qui permet de trouver le nombre le plus grand parmis
	 * quatres nombres passés en paramètres.
	 * @param p_Nb1 : Premier nombre.
	 * @param p_Nb2 : Deuxième nombre.
	 * @param p_Nb3 : Troisième nombre.
	 * @param p_Nb4 : Quatrième nombre.
	 * @return Le plus grand des quatre nombre.
	 */
	protected double maximum(double p_Nb1,double p_Nb2, double p_Nb3, double p_Nb4)
	{
	    double max = p_Nb1;
	    
	    if(p_Nb2 > max)
	        max = p_Nb2;
	    
	    if(p_Nb3 > max)
	        max = p_Nb3;
	    
	    if (p_Nb4 > max)
	        max = p_Nb4;
	    
	    return max;
	}
	
	/**
	 * Permet d'affecter une valeurs indiquant la récompense
	 * obtenue à long terme dans la direction parcourue dans déplacer le robot.
	 * @param p_TabCases : Cases entourant le robot.
	 * @param p_Direction : Direction dans laquelle le robot va se déplacer.
	 * @throws IllegalTableauDecisionException 
	 * @throws InvalidDirectionException 
	 * @throws IllegalPositionXException 
	 * @throws IllegalPositionYException 
	 */
	protected void memoriserValeurs(Case[] p_TabCases, int p_Direction) throws IllegalTableauDecisionException, InvalidDirectionException, IllegalPositionYException, IllegalPositionXException
	{
	    //On regarde si le tableau ne contient pas 4 cases.
        if (p_TabCases.length != 4)
            throw new IllegalTableauDecisionException();
        
        //On s'assure que la direction est valide
        if ((p_Direction < -1) || (p_Direction > 3))
            throw new InvalidDirectionException();
        
	    //On va chercher la récompense directe dans la case où on va se déplacer.
        int recompenseDirecte = p_TabCases[p_Direction].getRecompense();
        
	    int x_Courant = this.getX();
	    int y_Courant = this.getY();
	    
	    int x_ApresDeplacement = 0;
	    int y_ApresDeplacement = 0;
	    
	    //On simule le déplacement
	    switch(p_Direction)
	    {
    	    case HAUT:
    	        x_ApresDeplacement = this.getX();
    	        y_ApresDeplacement = this.getY()-1;
    	        break;
    	    case DROITE:
    	        x_ApresDeplacement = this.getX() +1;
                y_ApresDeplacement = this.getY();
    	        break;
    	    case BAS :
    	        x_ApresDeplacement = this.getX();
                y_ApresDeplacement = this.getY()+1;
    	        break;
    	    case GAUCHE:
    	        x_ApresDeplacement = this.getX() -1;
                y_ApresDeplacement = this.getY();
    	        break;
	    }
	    
	    //On va chercher les 4 valeurs qui sont affectées présentement dans la case où on a bouger (simulation).
        double valeurHaut = this.getCaseTab(x_ApresDeplacement, y_ApresDeplacement, HAUT);
        double valeurDroite = this.getCaseTab(x_ApresDeplacement, y_ApresDeplacement, DROITE);
        double valeurBas = this.getCaseTab(x_ApresDeplacement, y_ApresDeplacement, BAS);
        double valeurGauche = this.getCaseTab(x_ApresDeplacement, y_ApresDeplacement, GAUCHE);
        
        double qValeur = recompenseDirecte + (RobotIntelligent.GAMMA * this.maximum(valeurHaut, valeurDroite, valeurBas, valeurGauche));

        //On affecte notre qValeur à la case présédente d'où on était.
        this.setCaseTab(x_Courant, y_Courant, p_Direction, qValeur);
	    
	    
	}
}
